package com.jkrohn.io.countdowntimer.client;

import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.user.client.Random;

public class IOBalls {

	Canvas canvas;
	Context2d context;

	static final double twoPi = Math.PI * 2;
	static final CssColor greyPaint = CssColor.make(217, 217, 217);
	static final CssColor greenPaint = CssColor.make(0, 154, 73);
	static final CssColor redPaint = CssColor.make(192, 0, 11);
	static final CssColor purplePaint = CssColor.make(38, 88, 151);
	static final CssColor bluePaint = CssColor.make(19, 172, 250);
	static final CssColor backgroundPaint = CssColor.make(245, 245, 245);
	//originally 230
	static final CssColor binaryPaint = CssColor.make(225, 225, 225);

	/**
	 * This is the segments to light for each number from 0 to 9.
	 */
	static final short[] numberLEDSegments = { 1 | 2 | 4 | 8 | 16 | 32,// 0
			2 | 4,// 1
			1 | 2 | 8 | 16 | 64, // 2
			1 | 2 | 4 | 8 | 64, // 3
			2 | 4 | 32 | 64, // 4
			1 | 4 | 8 | 32 | 64, // 5
			1 | 4 | 8 | 16 | 32 | 64,// 6
			1 | 2 | 4,// 7
			1 | 2 | 4 | 8 | 16 | 32 | 64,// 8
			1 | 2 | 4 | 8 | 32 | 64 // 9
	};

	/**
	 * These are the arbitrarily numbered segments to allow us to in an extremely small amount of
	 * code light up the correct ones.
	 */
	static final short[][] ledSegments = new short[][] { { 1 | 32, 1, 1, 1 | 2 }, { 32, 0, 0, 2 },
			{ 32, 0, 0, 2 }, { 16 | 32 | 64, 64, 64, 2 | 4 | 64 }, { 16, 0, 0, 4 },
			{ 16, 0, 0, 4 }, { 8 | 16, 8, 8, 4 | 8 } };

	/**
	 * This contains all the previously lit segments, so that if one goes out, we can have balls fly
	 * off.
	 */
	static boolean[][][] lastLit = new boolean[9][7][4];

	static final long oneHour = 60 * 60 * 1000L;

	private static int daysBetween(long first, long second) {
		return (int) ((second - first + oneHour) / (oneHour * 24));
	}

	public static void updateTime() {
		now = System.currentTimeMillis();
	}

	public static int getDays() {
		return daysBetween(now, countDown);
	}

	public static int getHours() {
		return (int) ((countDown - now) % (oneHour * 24) / oneHour);
	}

	public static int getMinutes() {// we mod by hours, then divide by minutes
		return (int) ((countDown - now) % oneHour / (60 * 1000L));
	}

	public static int getSeconds() {
		return (int) ((countDown - now) % (60 * 1000L) / 1000L);
	}

	// these are all filled in the first time we start up.
	static int numberWidth, screenWidth, screenHeight, screenWidthHalf, dotSpacing;
	static float dotWidth;

	static long now = System.currentTimeMillis();
	static long countDown = System.currentTimeMillis();
	static long lastRender = System.currentTimeMillis();
	static long lastRenderDelta = now - lastRender;
	static int dotCount = 0;
	static boolean firstDraw = true;
	static boolean firstLED = true;
	static boolean checkBalls = false;
	final static int gravityPerSecond = 8;
	final static int maxBalls = 200;
	static int[] dotXs = new int[maxBalls];
	static int[] dotYs = new int[maxBalls];
	static int[] dotXspeed = new int[maxBalls];
	static int[] dotYspeed = new int[maxBalls];
	static byte[] dotColor = new byte[maxBalls];
	static int currentBalls = 0;
	// static Random generator = new Random();

	// static Rect screenRect;
	// stuff for accelerometer gravity
	static boolean accelGravity = false;
	// static Sensor sensor;
	// static SensorManager sensorManager;
	// if -1, then top is gravity, if 1 bottom is gravity
	static double verticalGravityDirection = 1;
	static double horizontalGravityDirection = 0;

	public IOBalls(Canvas canvas, int height, int width, long countDown) {
		this.canvas = canvas;
		this.screenHeight = height;
		this.screenWidth = width;
		this.countDown = countDown;
		this.context = canvas.getContext2d();
	}

	public void onDraw(Canvas canvas) {
		// super.onDraw(canvas);
		now = System.currentTimeMillis();
		if(now >= countDown) {
			//add another year to the countdown to prevent crashing.
			countDown += oneHour * 24 * 365;
		}
		if (firstDraw) {
			// set everything up to reduce computation time in future runs.
			if (canvas == null) {
				return;
			}
			numberWidth = screenWidth / 16;
			screenWidthHalf = screenWidth / 2;
			dotSpacing = numberWidth / 4;
			dotWidth = (float) ((float) dotSpacing / 2.3);
			firstDraw = false;
		}
		lastRenderDelta = now - lastRender;
		lastRender = now;
		// canvas.drawRect(screenRect, backgroundPaint);
		clearScreen(backgroundPaint);
		drawTime(canvas, getDays(), getHours(), getMinutes(), getSeconds());
		checkBalls = false;
		// next we get to ball physics
		// an odd incremental system, essentially each time we render we
		// check if we have an empty slot at the end, and if so decrement
		// the known number of balls we have.
		if (currentBalls > 0 && dotXs[currentBalls - 1] == Integer.MIN_VALUE) {
			currentBalls--;
		}
		CssColor temp;
		for (int i = 0; i < currentBalls; i++) {
			if (dotXs[i] != Integer.MIN_VALUE) {
				updateBall(i);
				switch (dotColor[i]) {
				case 0:
				case 1:
					temp = greenPaint;
					break;
				case 2:
				case 3:
					temp = redPaint;
					break;
				default:
					temp = bluePaint;
					break;
				}
				drawCircle(dotXs[i], screenHeight - dotYs[i], dotWidth, temp);
			}
		}

		firstLED = false;
	}

	void clearScreen(CssColor paint) {
		context.setFillStyle(paint);
		context.fillRect(0, 0, screenWidth, screenHeight);
	}

	void drawTime(Canvas canvas, final int days, final int hours, final int minutes, final int seconds) {
		// System.out.println("calling drawTime with " + days + "," + hours + "," + minutes + "," +
		// seconds);
		// System.out.println("now: " + Long.toString(now) + ", countDown: " +
		// Long.toString(countDown));
		//System.out.println(currentBalls);
		final int height = screenHeight / 2 - numberWidth * 2 / 3;
		int currentWidth = screenWidthHalf - dotSpacing * 2;
		// first we'll go from center to left end, handling hours & days
		drawNumber(canvas, hours % 10, currentWidth, height, bluePaint, true, 4);
		// next the second hours number
		currentWidth -= numberWidth + dotSpacing;
		drawNumber(canvas, hours / 10, currentWidth, height, bluePaint, false, 5);
		// next the first days number
		currentWidth -= numberWidth + dotSpacing * 3;
		drawNumber(canvas, days % 10, currentWidth, height, purplePaint, true, 6);
		// next second days number
		currentWidth -= numberWidth + dotSpacing;
		drawNumber(canvas, (days / 10) % 10, currentWidth, height, purplePaint, false, 7);
		// finally last days number
		currentWidth -= numberWidth + dotSpacing;
		drawNumber(canvas, (days / 100) % 1000, currentWidth, height, purplePaint, false, 8);
		// next we are going to the right, hence second minutes
		currentWidth = screenWidthHalf + dotSpacing * 5;
		drawNumber(canvas, minutes / 10, currentWidth, height, redPaint, false, 3);
		// first minutes
		currentWidth += numberWidth + dotSpacing;
		drawNumber(canvas, minutes % 10, currentWidth, height, redPaint, true, 2);
		// second seconds
		currentWidth += numberWidth + dotSpacing * 3;
		drawNumber(canvas, seconds / 10, currentWidth, height, greenPaint, false, 1);
		// first seconds
		currentWidth += numberWidth + dotSpacing;
		drawNumber(canvas, seconds % 10, currentWidth, height, greenPaint, false, 0);
		// easter egg fun!
		int delta = (int)((countDown - now) / 1000L);
		for(int i = 0; i < 25; i++) {
			//int binary = 1 << i;
			//binary = binary & delta;
			//if(binary != 0) {
			if(((1 << i) & delta) != 0) {
				//25 * 5 = 125 + 5 = 130
				drawCircle(130 - i * 7, 5, 4, binaryPaint);
			}
		}
	}

	void drawNumber(Canvas canvas, int number, int screenX, int screenY, CssColor activatedPaint,
			boolean drawDots, int place) {
		int dotX = screenX;
		int dotY = screenY;
		for (int x = 0; x < 4; x++) {
			for (int y = 0; y < 7; y++) {
				short ledSegment = ledSegments[y][x];
				short numSegment = numberLEDSegments[number];
				if ((ledSegment & numSegment) != 0) {
					lastLit[place][y][x] = true;
					drawCircle(dotX, dotY, dotWidth, activatedPaint);

				} else {
					drawCircle(dotX, dotY, dotWidth, greyPaint);
					if (firstLED) {
						lastLit[place][y][x] = false;
					}
					// if(checkBalls) {
					if (lastLit[place][y][x] == true) {
						// here we increase the number of balls generated
						// for how many
						// spaces over we create them.
						for (int i = 0; i < place + 1; i++) {
							// if we're here, then we need to issue a ball
							int newBall = 0;
							currentBalls = Math.min(maxBalls - 1, currentBalls + 1);
							// here we use any empty slots from balls that
							// disappeared off the screen.
							for (; newBall < currentBalls; newBall++) {
									if (dotXs[newBall] == Integer.MIN_VALUE) {
										if (currentBalls < maxBalls - 1) {
											currentBalls--;
										}
										break;
									}
							}
							lastLit[place][y][x] = false;
							dotXs[newBall] = dotX;
							dotYs[newBall] = dotY;
							dotXspeed[newBall] = Random.nextInt(20) - 10;
							// this way we never have balls stuck in one
							// place forever.
							if (dotXspeed[newBall] == 0) {
								dotXspeed[newBall] = -3;
							}
							// and here we prevent balls from super slowly
							// moving across screen
							if (Math.abs(dotXspeed[newBall]) < 3) {
								dotXspeed[newBall] = 4;
							}
							dotYspeed[newBall] = Random.nextInt(20) - 10;
							dotColor[newBall] = (byte) place;
							if (currentBalls < maxBalls - 1) {
								currentBalls++;
							}
						}
					}
				}
				dotY += dotSpacing;
			}
			dotX += dotSpacing;
			dotY = screenY;
		}
		if (drawDots) {
			dotX = (int) (screenX + dotSpacing * 5);
			dotY = (int) (screenY + dotSpacing * 2);
			drawCircle(dotX, dotY, dotWidth, greyPaint);
			drawCircle(dotX, dotY + dotSpacing * 2, dotWidth, greyPaint);
		}
	}

	void createRandomBall(int x, int y) {
		// if we're here, then we need to issue a ball
		int newBall = currentBalls;
		currentBalls = Math.min(maxBalls - 1, currentBalls + 1);
		// here we use any empty slots from balls that
		// disappeared off the screen
		dotXs[newBall] = x;
		dotYs[newBall] = y;
		dotXspeed[newBall] = Random.nextInt(20) - 10;
		// this way we never have balls stuck in one
		// place forever.
		if (dotXspeed[newBall] == 0) {
			dotXspeed[newBall] = -3;
		}
		// and here we prevent balls from super slowly
		// moving across screen
		if (Math.abs(dotXspeed[newBall]) < 3) {
			dotXspeed[newBall] = 4;
		}
		dotYspeed[newBall] = Random.nextInt(20) - 10;
		dotColor[newBall] = (byte) Random.nextInt(6);
		if (currentBalls < maxBalls - 1) {
			currentBalls++;
		}
	}

	void drawCircle(int x, int y, float width, CssColor color) {
		context.setFillStyle(color);
		context.beginPath();
		context.arc(x, y, width, 0, twoPi, true);
		context.closePath();
		context.fill();
	}

	void drawText(String text, int x, int y) {
		context.fillText(text, x, y);
	}

	/**
	 * This is the quick and dirty physics method I wrote up for this competition. Basically this
	 * increments the location of the ball according to it's current speed, and will reverse the y
	 * direction if it hits the floor. It also slightly decrements the vertical speed each time it
	 * hits the ground. It uses exclusively integer math for 'wicked' speed.
	 * 
	 * @param number
	 *            The index of the ball to update the position & speed of.
	 */
	static void updateBall(int number) {

		int vertGravPerSec, horzGravPerSec;
		// if we are using the accelerometer, we use these for changing speed
		if (accelGravity) {
			vertGravPerSec = (int) (gravityPerSecond * verticalGravityDirection);
			horzGravPerSec = (int) (gravityPerSecond * horizontalGravityDirection);
		} else {
			vertGravPerSec = gravityPerSecond;
			horzGravPerSec = 0;
		}
		if (horzGravPerSec != 0) {
			dotXspeed[number] = (int) (dotXspeed[number] - horzGravPerSec * lastRenderDelta / 100);
		}
		dotXs[number] += dotXspeed[number] * lastRenderDelta / 20;
		// if the ball has bounced out of view
		if (dotXs[number] <= 0 || dotXs[number] >= screenWidth) {
			dotXs[number] = Integer.MIN_VALUE;
		}
		dotYspeed[number] = (int) (dotYspeed[number] - vertGravPerSec * lastRenderDelta / 100);
		dotYs[number] += (dotYspeed[number]) * lastRenderDelta / 20;
		if (dotYs[number] < 1 || dotYs[number] > screenHeight - 1) {
			// get it out of the floor/ceiling
			if (dotYs[number] < 1) {
				dotYs[number] *= -1;
			} else {
				dotYs[number] = screenHeight - (dotYs[number] - screenHeight);
			}
			// reverse it's speed
			dotYspeed[number] *= -1;
			// and slow it's speed down.
			if (dotYspeed[number] > 0) {
				dotYspeed[number] -= 1;
			} else {
				dotYspeed[number] += 1;
			}
		}
	}

}
